/** 
 * 
 * Licensed under the MIT License and Apache License version 2.0
 * 
 * Copyright (c) 2011 Jazz Digital (http://www.jazz.etc.br) 
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * http://code.google.com/p/kinect-jazztures/
 * 
 * @author Valério Oliveira (valck) - http://www.valck.in
 * @author João Marquesini - http://www.markezine.me
 * @author Gabriel Tavares de Souza - http://www.gtsouza.com
 * 
 */
package jazztures {

	import jazztures.events.CursorEvent;
	import jazztures.events.GestureEvent;
	import jazztures.events.SkeletonEvent;
	import jazztures.events.SocketEvent;
	import jazztures.events.UserEvent;

	import com.adobe.serialization.json.JSON;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.net.Socket;
	import flash.utils.setTimeout;
	

	/**
	 * 
	 * Creates a new Jazztures object, the parameters are specified or not, a connection is attempted to the specified jazztures server (host and port).
	 * 
	 */
	public class Jazztures extends EventDispatcher{
		
		private static const USER : String = "user";
		private static const MOTOR : String = "motor";
		private static const DEPTH : String = "depth";
		private static const CURSOR : String = "cursor";
		private static const GESTURE : String = "gesture";
		
		private var host : String;
		private var port : uint;
		private var timeout : uint;

		private var autoReconnect : Boolean;
		private var debug : Boolean;

		private var socket : Socket;
		private var data : Object;
		
		/**
		 * 
		 * Creates a new Jazztures object, the parameters are specified or not, a connection is attempted to the specified jazztures server (host and port).  
		 * 
		 * @param p_host A fully qualified DNS domain name or an IP address. IPv4 addresses are specified in dot-decimal notation, such as 192.0.2.0. In Flash Player 10 and later. 
		 * @param p_port The TCP port number on the target host used to establish a connection.  
		 * @param p_timeout Indicates the number of milliseconds to wait for a reconnection.
		 * @param p_reconnect If the connection failed or disconnected, the reconnection was attempted.
		 * @default By default the host is 127.0.0.1 and the port is 666.
		 * 
		 */
		public function Jazztures(p_host:String="127.0.0.1", p_port:uint=666, p_timeout:uint=1000, p_reconnect:Boolean=true){
			host = p_host;
			port = p_port;
			timeout = p_timeout;
			autoReconnect = p_reconnect;
			
			data = new Object();
			socket = new Socket();
			
			socket.addEventListener(Event.CLOSE, onDisconnect);
			socket.addEventListener(Event.CONNECT, onConnect);
			socket.addEventListener(ProgressEvent.SOCKET_DATA, onReceiveData);
			socket.addEventListener(IOErrorEvent.IO_ERROR, onError);
		}

		/**
		 * 
		 * Disconnect of server. You cannot read or write any data after the <code>disconnect()</code> method has been called. 
		 * 
		 */
		public function disconnect():void {
			socket.connected ? socket.close() : null;
		}
		
		/**
		 * 
		 * Connect on server. If the connection fails, a error event is dispatched. 
		 * 
		 * @param p_debug Active the log if <code>true</code> and <code>false</code> otherwise. 
		 * @default <code>true</code>
		 * 
		 */
		public function connect(p_debug:Boolean=true):void {
			debug = p_debug;
			
			!socket.connected && host && port ? socket.connect(host, port) : null;
		}
		
		/**
		 * 
		 * Send a data in JSON format to the server. 
		 * 
		 * @param p_data The object in JSON format to write to the server.
		 * @see JSON
		 * @see Object
		 * 
		 */
		public function sendData(p_data:Object):void {
			if (!socket.connected) return;
			
			socket.writeUTFBytes(String(JSON.encode(p_data)));
			socket.flush();
			
			log("sending data:", p_data);
		}
		
		/**
		 * 
		 * @private
		 * Dispatched when the reconnection is attempted.
		 * 
		 */
		private function reconnect():void {
			dispatchEvent(new SocketEvent(SocketEvent.RECONNECTED));
			log("restart connection...\n\n");
			setTimeout(connect, timeout);
		}
		
		/**
		 * 
		 * @private
		 * Dispatched when the server closes the connection.
		 * 
		 * @param evt <code>Event</code>
		 */
		private function onDisconnect(evt:Event):void{
			dispatchEvent(new SocketEvent(SocketEvent.DISCONNECTED));
			autoReconnect ? reconnect() : log("connection closed.");
		}
		
		/**
		 * 
		 * @private
		 * Dispatched when a connection has been established. 
		 * 
		 * @param evt <code>Event</code>
		 * 
		 */
		private function onConnect(evt:Event):void{
			dispatchEvent(new SocketEvent(SocketEvent.CONNECTED));
			log("connection open.");
		}
		
		/**
		 * 
		 * @private
		 * Dispatched if an input/output error occurs that causes the connection to fail. 
		 * 
		 * @param evt <code>IOErrorEvent</code>
		 * @see IOErrorEvent
		 * 
		 */
		private function onError(evt:IOErrorEvent):void{
			dispatchEvent(new SocketEvent(SocketEvent.ERROR));
			autoReconnect ? reconnect() : null;
		}
		
		private function trim(p_str:String):String{
			return p_str.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " ");
		}
				
		/**
		 * 
		 * @private
		 * Reads the data specified by the <code>bytesAvailable</code> from the server, and dispatch a correspondent event like <code>CursorEvent</code>, <code>UserEvent</code>, <code>SkeletonEvent</code> or <code>GestureEvent</code>.
		 * 
		 * @param evt <code>Event</code>
		 * 
		 */
		private function onReceiveData(evt:Event):void{
			if (!socket.connected) return;
			
			try{
				data = JSON.decode(trim(socket.readUTFBytes(socket.bytesAvailable)));
				data = data.object;
				
				for (var obj : * in data){
					switch(obj){
						case Jazztures.CURSOR:
							switch(data[obj].action){
								
								/*
								 * Dispatched when a cursor is detected by server.
								 */
								case "add":
									dispatchEvent(new CursorEvent(CursorEvent.ADD_CURSOR, data[obj].id, data[obj].x, data[obj].y, data[obj].z));
									break;

								/*
								 * Dispatched when a cursor was updated by server.
								 */
								case "update":
									dispatchEvent(new CursorEvent(CursorEvent.UPDATE_CURSOR, data[obj].id, data[obj].x, data[obj].y, data[obj].z));
									break;
	
								/*
								 * Dispatched when a cursor was lost by server.
								 */
								case "remove":
									dispatchEvent(new CursorEvent(CursorEvent.REMOVE_CURSOR, data[obj].id));
									break;
							}
							break;
						
						case Jazztures.USER:
							switch(data[obj].action){
								
								/*
								 * Dispatched when a user is detected by server.
								 */
								case "add":
									dispatchEvent(new UserEvent(UserEvent.ADD_USER, data[obj].id, data[obj].state));
									break;

								/*
								 * Dispatched when a user was lost by server.
								 */
								case "remove":
									dispatchEvent(new UserEvent(UserEvent.REMOVE_USER, data[obj].id, data[obj].state));
									break;

								/*
								 * Dispatched when a skeleton is detected by server.
								 */
								case "addskeleton":
									dispatchEvent(new SkeletonEvent(SkeletonEvent.ADD_SKELETON, data[obj].id, data[obj].skeleton));
									break;

								/*
								 * Dispatched when a skeleton was updated by server.
								 */
								case "updateskeleton":
									dispatchEvent(new SkeletonEvent(SkeletonEvent.UPDATE_SKELETON, data[obj].id, data[obj].skeleton));
									break;
		
								/*
								 * Dispatched when a skeleton was lost by server.
								 */
								case "removeskeleton":
									dispatchEvent(new SkeletonEvent(SkeletonEvent.REMOVE_SKELETON, data[obj].id, data[obj].skeleton));
									break;
							}
							break;
						case Jazztures.GESTURE:
							switch(data[obj].type){
								
								/*
								 * Dispatched when a tap gesture is detected by server.
								 */
								case "click":
									dispatchEvent(new GestureEvent(GestureEvent.TAP, data[obj].x, data[obj].y, data[obj].z));
									break;

								/*
								 * Dispatched when a wave gesture is detected by server.
								 */
								case "wave":
									dispatchEvent(new GestureEvent(GestureEvent.WAVE, data[obj].x, data[obj].y, data[obj].z));
									break;

								/*
								 * Dispatched when a raise gesture is detected by server.
								 */
								case "raiseHand":
									dispatchEvent(new GestureEvent(GestureEvent.RAISE_HAND, data[obj].x, data[obj].y, data[obj].z));
									break;
							}
							break;
						
						/*
						 * TODO
						 */	
						case Jazztures.DEPTH:
							log("depth:", data[obj]);
							break;

						/*
						 * TODO
						 */	
						case Jazztures.MOTOR:
							log("motor:", data[obj]);
							break;
					}
				}
			}catch(e:Error){
				trace("Parser Error...", e.getStackTrace());
			}
		}
		
		/**
		 * 
		 * @private
		 * Send information of input/output data on server.
		 * 
		 * @param p_message The string message to write.
		 * 
		 */
		private function log(...p_message):void{
			debug ? trace(String(p_message).split(",").join(" ")) : null;
		}
	}
}